#region copyright
/*
* Copyright (c) 2009, Dion Kurczek
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY DION KURCZEK ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL DION KURCZEK BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using SCG.General;

namespace SCG.Prism.Server
{
    //PrismGuest class encapsulates a single client connected to PrismServer
    public class PrismGuest
    {
        //Constructor
        public PrismGuest(PrismServer server, PrismNetworkStream pns, TcpClient socket)
        {           
            _prismStream = pns;
            _user = new PrismUser();
            _server = server;

            //Grab IP address and store for future reference
            IPEndPoint ipend = (IPEndPoint)socket.Client.RemoteEndPoint;
            _iPAdress = ipend.Address;
        }

        //PrismUser property provides access to the user data for this client
        public PrismUser User
        {
            get
            {
                return _user;
            }
        }

        //SubjectName contains the application subject the client is connected with
        public string SubjectName
        {
            get
            {
                return _subjectName;
            }
            set
            {
                _subjectName = value;
            }
        }

        //Access the PrismNetworkStream
        public PrismNetworkStream Stream
        {
            get
            {
                return _prismStream;
            }
        }

        //Access the room they are in - send appropriate messages when changing rooms
        public PrismRoom Room
        {
            get
            {
                return _room;
            }
            set
            {
                //Is he in another room?
                if (_room != null)
                {
                    //Remove him
                    _room.RemoveGuest(this);

                    //is the old room now empty?
                    if (_room.RoomGuests.Count == 0 && !_room.Persistent)
                        _server.RemoveRoom(_room);

                    //alert other guests in old room that he is leaving                  
                    else
                    {
                        _room.SendToGuests("LEAVEROOM", User.UserName);

                        //and let all members of subject know the room count changed
                        _server.SendToSubject(SubjectName, "ROOMCOUNTCHANGE", _room.RoomName, _room.RoomGuests.Count.ToString());
                    }
                }
                //Is his new room a "real" room?
                if (value != null)
                {
                    //Yes, tell current guests there that he is entering
                    value.SendToGuests("ENTERROOM", User.ToString());

                    //Actually add him to the room
                    value.AddGuest(this);

                    //Notify all members of subject that the room state changed
                    _server.SendToSubject(SubjectName, "ROOMCOUNTCHANGE", value.RoomName, value.RoomGuests.Count.ToString());
                }
                //Assign the local variable
                _room = value;

                //If it is a "real" room, send guest the initialization message
                if (_room != null)
                {
                    Tokenizer tok = new Tokenizer();
                    string s;
                    lock (_room.RoomGuestLock)
                    {
                        foreach(PrismGuest guest in _room.RoomGuests)
                        {
                            s = guest.User.ToString();
                            tok.AppendToken(s);                           
                        }
                    }
                    s = tok.Result;
                    try
                    {
                        WriteTokens("JOINROOM", _room.RoomName, s);
                    }
                    catch
                    {
                    }
                }
            }
        }

        //Access their IP Address
        public IPAddress IPAddress
        {
            get
            {
                return _iPAdress;
            }
        }

        //Is the Guest object waiting to return a ping?
        public bool WaitingForPing
        {
            get
            {
                return _waitingForPing;
            }
        }

        //Return the latency of the last ping request
        public TimeSpan Latency
        {
            get
            {
                return _latency;
            }
        }

        //Was the Guest already removed from the server?
        public bool WasRemoved
        {
            get
            {
                return _wasRemoved;
            }
            set
            {
                _wasRemoved = value;
            }
        }

        //WriteTokens method - writes a command, catches exception, removes Guest from list on error
        public void WriteTokens(params object[] tokens)
        {
            try
            {
                Stream.WriteTokens(tokens);             
            }
            catch
            {
                _server.RemoveGuest(this);
            }
        }

        //Write a custom command
        public void CustomCommand(string commandName, string param)
        {
            WriteTokens("CUSTOM", commandName, param);
        }

        //The following methods support the Prism Protocol

        //Ping the client - set flag indicating that a ping reply is pending
        public void Ping()
        {
            _pinged = DateTime.Now;
            _waitingForPing = true;
            WriteTokens("PING");
        }

        //A response Ping was captures - record latency of response
        public void PingReturned()
        {
            _latency = DateTime.Now - _pinged;
            _user.LatencyMS = (int)_latency.TotalMilliseconds;
            if (_room != null)
                _room.SendToGuests("LATENCY", _user.UserName, _user.LatencyMS.ToString());
            _waitingForPing = false;
        }

        //Send an admin message to this client
        public void SendAdminMessage(string msg)
        {
            WriteTokens("ADMINMSG", msg);
        }

        //String representation
        public override string ToString()
        {
            if (_user != null && _user.UserName != "")
                return _user.UserName + " {" + _iPAdress.ToString() + "}";
            else
                return _iPAdress.ToString();
        }

        //Private members        
        private string _subjectName = "";
        private PrismUser _user;
        private PrismServer _server;
        private PrismNetworkStream _prismStream;
        private DateTime _pinged;
        private bool _waitingForPing = false;
        private TimeSpan _latency = new TimeSpan();
        private PrismRoom _room = null;
        private IPAddress _iPAdress;
        private bool _wasRemoved;
    }
    //Exception class used to indicate errors related to PrismGuests
    public class PrismGuestException : ApplicationException
    {
        private PrismGuest _guest;

        public PrismGuestException(PrismGuest guest, string message)
            : base(message)
        {
            _guest = guest;
        }

        public PrismGuest Guest
        {
            get
            {
                return _guest;
            }
        }
    }

    //EventArgs class allowing PrismGuests to be used in event parameters
    public class PrismGuestEventArgs : EventArgs
    {
        private PrismGuest _guest;

        public PrismGuestEventArgs(PrismGuest guest)
        {
            _guest = guest;
        }

        public PrismGuest Guest
        {
            get
            {
                return _guest;
            }
        }
    }
    public class PrismGuestMsgEventArgs : EventArgs
    {
        //Private members
        private PrismGuest _guest;
        private string _msg;

        //Constructor
        public PrismGuestMsgEventArgs(PrismGuest guest, string msg)
        {
            _guest = guest;
            _msg = msg;
        }

        //Public properties
        public PrismGuest Guest
        {
            get
            {
                return _guest;
            }
        }
        public string Msg
        {
            get
            {
                return _msg;
            }
        }
    }
}
